/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  internal class EnumeratorHelper : DependencyObject
  {
    #region DataEnumerator Property

    internal DataSourceEnumeratorWrapper DataEnumerator
    {
      get
      {
        return m_dataSourceEnumerator;
      }
      set
      {
        if( m_dataSourceEnumerator != value )
        {
          m_dataSourceEnumerator = value;
          this.Initialize();
        }
      }
    }

    #endregion

    #region CurrentDataPath Property

    internal DataPath CurrentDataPath
    {
      get
      {
        return m_currentDataPath;
      }
      set
      {
        if( m_currentDataPath != value )
        {
          m_currentDataPath = value;
        }
      }
    }

    #endregion

    #region CurrentIndexProperty

    internal long CurrentIndex
    {
      get
      {
        return m_currentIndex;
      }
    }

    #endregion

    #region Internal Methods

    internal void Initialize()
    {
      this.Move( 1 );
    }

    internal bool JumpMoveAndFetch( DataPath targetPath, long moveCount, int fetchCount )
    {
      ICollection<DataPath> fetchedItems;

      if( m_dataSourceEnumerator == null )
        return false;

      // Jump operation
      bool retVal = m_dataSourceEnumerator.JumpMoveAndFetch( targetPath, moveCount, fetchCount, out fetchedItems );

      if( retVal )
      {
        // If the operation succeded, update the 
        // currentDataPath and the currentIndex
        m_currentDataPath = fetchedItems.First();
        m_currentIndex = m_dataSourceEnumerator.Index;
        this.OnMoveCompleted();
      }

      return retVal;
    }

    internal bool JumpMoveAndFetch( DataPath targetPath, long moveCount )
    {
      return this.JumpMoveAndFetch( targetPath, moveCount, 1 );
    }

    internal bool JumpMoveAndFetch( DataPath targetPath )
    {
      return this.JumpMoveAndFetch( targetPath, 0 );
    }

    // Method used to keep track of the current DataPath and current index
    // of the DataSourceEnumerator to be able to correctly update the offset
    // of the ScrollBar
    internal bool Move( long moveCount )
    {
      return this.Move( moveCount, false );
    }

    internal bool Move( long moveCount, bool areSkippedItemsLayouted )
    {
      if( m_dataSourceEnumerator == null )
        return false;

      long targetIndex = m_currentIndex + moveCount;
      int fetchCount = ( Math.Sign( moveCount ) >= 0 ) ? 1 : -1;

      // Since the items that are about to be skipped are still of some interest,
      // we shouldn't move to far away if we want some sort of caching to occur.
      if( areSkippedItemsLayouted )
      {
        if( Math.Abs( moveCount ) > 1 )
        {
          if( ( moveCount < Int32.MinValue ) || ( moveCount > Int32.MaxValue ) )
            throw new ArgumentOutOfRangeException( "moveCount", "moveCount must be greater than or equal to Int32.MinValue and less than or equal to Int32.MaxValue." );

          fetchCount = ( int )moveCount;
          moveCount = Math.Sign( moveCount );
        }
      }

      ICollection<DataPath> fetchedItems;
      bool retval = m_dataSourceEnumerator.MoveAndFetch( moveCount, fetchCount, out fetchedItems );

      if( ( fetchedItems != null ) && ( fetchedItems.Count > 0 ) )
      {
        m_currentDataPath = fetchedItems.Last();
      }

      m_currentIndex = m_dataSourceEnumerator.Index;

      if( m_currentIndex != targetIndex )
      {
        this.OnMoveCompleted();
      }

      return retval;
    }

    internal void InvalidateCurrentDataPath()
    {
      m_currentDataPath = null;
    }

    internal void EnsurePositionFromSourcePosition( SourcePosition sourcePosition )
    {
      // No need to ensure the position
      if( this.m_dataSourceEnumerator.ItemCount == 0 )
        return;

      if( sourcePosition.IsAfterLast )
      {
        if( !this.Move( -1 ) )
          throw new ListBoxInternalException( "Cannot move back." );
      }
      else if( sourcePosition.IsBeforeFirst )
      {
        if( !this.Move( 1 ) )
          throw new ListBoxInternalException( "Cannot move forward." );
      }
    }

    internal void EnsureDataSourceEnumeratorPosition()
    {
      long index = m_dataSourceEnumerator.Index;

      if( m_dataSourceEnumerator.ItemCount == index )
      {
        this.Move( -1 );
      }
      else if( index == -1 )
      {
        // We must reset local fields in order to 
        // avoid Index and CurrentDataPath inconsistency
        m_currentDataPath = null;
        m_currentIndex = -1;
        this.Move( 1 );
      }
      else
      {
        this.Move( 0 );
      }
    }

    internal void RefreshCurrentDataPath()
    {
      if( this.CurrentDataPath != null )
        return;

      if( !this.Move( 1 ) )
      {
        if( !this.Move( -1 ) )
        {
          if( m_dataSourceEnumerator.ItemCount != 0 )
            throw new ListBoxInternalException( "Unable to position the DataSourceEnumerator to obtain current in a source where the ItemCount is greater than 0." );
        }
      }
    }

    #endregion

    #region Private Methods

    private void OnMoveCompleted()
    {
      if( this.MoveCompleted != null )
      {
        this.MoveCompleted( this, new MoveCompletedEventArgs( m_currentIndex ) );
      }
    }

    #endregion

    #region Events

    internal event EventHandler<MoveCompletedEventArgs> MoveCompleted;

    #endregion

    #region Private Members

    private DataPath m_currentDataPath;
    private long m_currentIndex;

    private DataSourceEnumeratorWrapper m_dataSourceEnumerator;

    #endregion
  }
}
